Skip to content

SwiftUI之文字超出行数的支持展开收起

需求:文本超过10行时,需要支持折叠、收起。
要点:需要计算文本是否已经溢出

方案1:
使用GeometryReader计算是否超过范围

方案2:
使用iOS16的ViewThatFits

伪代码:

swift
struct ExpandableTextView: View {
    var text: String
    @Binding var expaned: Bool
    @Binding var isTruncated: Bool

    init(text: String,
         expaned: Binding<Bool>,
         isTruncated: Binding<Bool>)
    {
        self.text = text
        self._expaned = expaned
        self._isTruncated = isTruncated
    }

    var body: some View {
        if #available(iOS 16.0, *), false {
            ParmaView(content: text)
                .lineLimit(expaned ? nil : 10)
                .frame(maxWidth: .infinity)
                .background(
                    ViewThatFits(in: .vertical) {
                        ParmaView(content: text)
                            .hidden()
                        Color.clear // text doesn't fit
                            .onAppear {
                                isTruncated = true
                            }
                    }
                )
        } else {
            ParmaView(content: self.text)
                .frame(maxWidth: .infinity)
                .lineLimit(self.expaned ? nil : 10)
                .background(GeometryReader { geometry in
                    Color.clear.onAppear {
                        self.determineTruncation(geometry, text: self.text)
                    }
                })
        }
    }

    // https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text
    private func determineTruncation(_ geometry: GeometryProxy, text: String) {
        // Calculate the bounding box we'd need to render the
        // text given the width from the GeometryReader.
        let total = text.boundingRect(
            with: CGSize(
                width: geometry.size.width,
                height: .greatestFiniteMagnitude
            ),
            options: .usesLineFragmentOrigin,
            attributes: [
                .font: UIFont.systemFont(ofSize: 18),
            ],
            context: nil
        )
        if total.size.height > geometry.size.height {
            self.isTruncated = true
        }
    }
}
struct ExpandableTextView: View {
    var text: String
    @Binding var expaned: Bool
    @Binding var isTruncated: Bool

    init(text: String,
         expaned: Binding<Bool>,
         isTruncated: Binding<Bool>)
    {
        self.text = text
        self._expaned = expaned
        self._isTruncated = isTruncated
    }

    var body: some View {
        if #available(iOS 16.0, *), false {
            ParmaView(content: text)
                .lineLimit(expaned ? nil : 10)
                .frame(maxWidth: .infinity)
                .background(
                    ViewThatFits(in: .vertical) {
                        ParmaView(content: text)
                            .hidden()
                        Color.clear // text doesn't fit
                            .onAppear {
                                isTruncated = true
                            }
                    }
                )
        } else {
            ParmaView(content: self.text)
                .frame(maxWidth: .infinity)
                .lineLimit(self.expaned ? nil : 10)
                .background(GeometryReader { geometry in
                    Color.clear.onAppear {
                        self.determineTruncation(geometry, text: self.text)
                    }
                })
        }
    }

    // https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text
    private func determineTruncation(_ geometry: GeometryProxy, text: String) {
        // Calculate the bounding box we'd need to render the
        // text given the width from the GeometryReader.
        let total = text.boundingRect(
            with: CGSize(
                width: geometry.size.width,
                height: .greatestFiniteMagnitude
            ),
            options: .usesLineFragmentOrigin,
            attributes: [
                .font: UIFont.systemFont(ofSize: 18),
            ],
            context: nil
        )
        if total.size.height > geometry.size.height {
            self.isTruncated = true
        }
    }
}